﻿#region Copyright (c) 2012-11, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

#if TESTS

using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;


namespace Amarok.Agents
{
	[TestFixture]
	public class Test_Operation_TResult_TProgress
	{
		[SetUp]
		public void TestSetup()
		{
			_Reset();
		}

		[Serializable]
		public class MyOperation : Operation<String, Int32>
		{
			public Int32 Value
			{
				get;
				private set;
			}

			public MyOperation(Int32 value)
			{
				this.Value = value;
			}
		}


		private Int32 mProgressHandlerCalled;
		private Int32 mProgressHandlerValue;
		private AutoResetEvent mProgressHandlerCalledSignal;
		private Int32 mResultHandlerCalled;
		private Task<String> mResultHandlerValue;
		private AutoResetEvent mResultHandlerCalledSignal;
		private Boolean mProgressHandlerThrowException;


		private void _Reset()
		{
			mProgressHandlerCalled = 0;
			mProgressHandlerValue = -1;
			mProgressHandlerCalledSignal = new AutoResetEvent(false);
			mResultHandlerCalled = 0;
			mResultHandlerValue = null;
			mResultHandlerCalledSignal = new AutoResetEvent(false);
			mProgressHandlerThrowException = false;
		}

		private void _SetupProgressHandler(Operation<String, Int32> operation, Int32 expectedCount = 1)
		{
			operation.Progress += p =>
				{
					mProgressHandlerValue = p;
					mProgressHandlerCalled++;

					if (mProgressHandlerCalled == expectedCount)
						mProgressHandlerCalledSignal.Set();

					if (mProgressHandlerThrowException)
						throw new ApplicationException("PROGRESS");
				};
		}

		private void _SetupTaskHandler(Operation<String, Int32> operation)
		{
			operation.Completion.ContinueWith(task =>
				{
					mResultHandlerValue = task;
					mResultHandlerCalled++;
					mResultHandlerCalledSignal.Set();
				});
		}


		// --- TESTS ---


		[Test]
		public void Test_ReportProgress()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			operation.ReportProgress(10);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(5000), Is.True);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));
			Assert.That(mProgressHandlerValue, Is.EqualTo(10));
		}

		[Test]
		public void Test_ReportProgress_Handler_ThrewException()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			mProgressHandlerThrowException = true;

			operation.ReportProgress(10);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(5000), Is.True);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));
			Assert.That(mProgressHandlerValue, Is.EqualTo(10));
		}

		[Test]
		public void Test_ReportProgress_MultipleValues()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation, 10000);
			_SetupTaskHandler(operation);

			for (Int32 i = 0; i < 10000; i++)
				operation.ReportProgress(i);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(5000), Is.True);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(10000));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));
			Assert.That(mProgressHandlerValue, Is.EqualTo(9999));
		}

		[Test]
		public void Test_ReportProgress_MultipleValues_RegisterAfterReporting()
		{
			var operation = new MyOperation(123);

			_SetupTaskHandler(operation);

			for (Int32 i = 0; i < 10000; i++)
				operation.ReportProgress(i);

			Thread.Sleep(250);

			_SetupProgressHandler(operation, 1);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));
		}

		[Test]
		public void Test_ReportProgress_After_Complete()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			operation.Complete("done");
			operation.ReportProgress(10);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mResultHandlerCalledSignal.WaitOne(5000), Is.True);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_ReportProgress_MultipleHandler()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			var handlerCalled = 0;
			var handlerValue = -1;
			var handlerSignal = new AutoResetEvent(false);

			var handler = new Action<Int32>(p =>
				{
					handlerValue = p;
					handlerCalled++;
					handlerSignal.Set();
				});

			operation.Progress += handler;
			operation.ReportProgress(10);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(5000), Is.True);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(1));
			Assert.That(mProgressHandlerValue, Is.EqualTo(10));
			Assert.That(handlerSignal.WaitOne(5000), Is.True);
			Assert.That(handlerCalled, Is.EqualTo(1));
			Assert.That(handlerValue, Is.EqualTo(10));

			operation.Progress -= handler;
			operation.ReportProgress(10);

			Assert.That(handlerSignal.WaitOne(500), Is.False);
			Assert.That(handlerCalled, Is.EqualTo(1));
			Assert.That(handlerValue, Is.EqualTo(10));
		}

		[Test]
		public void Test_ReportProgress_AfterDispose()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			Helper.Dispose(operation);
			operation.ReportProgress(10);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));
		}

		[Test]
		public void Test_ReportProgress_CallbackThrowingException()
		{
			var operation = new MyOperation(123);

			operation.Progress += p =>
				{
					throw new ApplicationException();
				};

			Assert.DoesNotThrow(() =>
				{
					operation.ReportProgress(10);

					operation.Complete("DONE");
					operation.Completion.Wait(5000);
				});
		}

		[Test]
		public void Test_CompleteCanceled()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			operation.CompleteCanceled();

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalledSignal.WaitOne(5000, false), Is.True);
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerValue, Is.Not.Null);
			Assert.That(mResultHandlerValue.IsCanceled, Is.True);
		}

		[Test]
		public void Test_CompleteFaulted()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			var exception = new ApplicationException();
			operation.CompleteFaulted(exception);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalledSignal.WaitOne(5000, false), Is.True);
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerValue, Is.Not.Null);
			Assert.That(mResultHandlerValue.IsFaulted, Is.True);
			Assert.That(mResultHandlerValue.Exception.InnerException, Is.SameAs(exception));
		}

		[Test]
		public void Test_CompleteFaulted_TaskExceptionOnFinalizerThread()
		{
			var operation = new MyOperation(123);
			var exception = new ApplicationException();
			operation.CompleteFaulted(exception);

			operation = null;

			Thread.Sleep(500);

			GC.Collect(2);
			GC.WaitForPendingFinalizers();
		}

		[Test]
		public void Test_CompleteFaulted_NullException()
		{
			var operation = new MyOperation(123);

			Assert.Throws<ArgumentNullException>(() =>
				{
					operation.CompleteFaulted(null);
				});
		}

		[Test]
		public void Test_Complete()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			var result = "done";
			operation.Complete(result);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalledSignal.WaitOne(5000, false), Is.True);
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerValue, Is.Not.Null);
			Assert.That(mResultHandlerValue.IsCanceled, Is.False);
			Assert.That(mResultHandlerValue.IsFaulted, Is.False);
			Assert.That(mResultHandlerValue.Result, Is.EqualTo(result));
		}

		[Test]
		public void Test_Complete_Twice()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			operation.Complete("first");

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalledSignal.WaitOne(5000, false), Is.True);
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerValue, Is.Not.Null);
			Assert.That(mResultHandlerValue.IsCanceled, Is.False);
			Assert.That(mResultHandlerValue.IsFaulted, Is.False);
			Assert.That(mResultHandlerValue.Result, Is.EqualTo("first"));

			Assert.Throws<InvalidOperationException>(() =>
				{
					operation.Complete("second");
				});

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalledSignal.WaitOne(500, false), Is.False);
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_Complete_RegisterAfterCompletion()
		{
			var operation = new MyOperation(123);

			var result = "done";
			operation.Complete(result);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalledSignal.WaitOne(5000, false), Is.True);
			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerValue, Is.Not.Null);
			Assert.That(mResultHandlerValue.IsCanceled, Is.False);
			Assert.That(mResultHandlerValue.IsFaulted, Is.False);
			Assert.That(mResultHandlerValue.Result, Is.EqualTo(result));
		}

		[Test]
		public void Test_Complete_MultipleHandler()
		{
			var operation = new MyOperation(123);

			_SetupProgressHandler(operation);
			_SetupTaskHandler(operation);

			Task<String> handlerValue = null;
			Int32 handlerCalled = 0;

			operation.Completion.ContinueWith(task =>
				{
					handlerValue = task;
					handlerCalled++;
				});

			var result = "done";
			operation.Complete(result);

			var waitResult = operation.Completion.Wait(5000);

			Assert.That(waitResult, Is.True);
			Assert.That(operation.Completion.IsCanceled, Is.False);
			Assert.That(operation.Completion.IsFaulted, Is.False);
			Assert.That(operation.Completion.Result, Is.EqualTo(result));

			Assert.That(mResultHandlerCalledSignal.WaitOne(5000, false), Is.True);
			Assert.That(Helper.WaitForCondition(5000, 10, () => handlerCalled == 1), Is.True);

			Assert.That(mProgressHandlerCalledSignal.WaitOne(500), Is.False);
			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));

			Assert.That(mResultHandlerCalled, Is.EqualTo(1));
			Assert.That(mResultHandlerValue, Is.Not.Null);
			Assert.That(mResultHandlerValue.IsCanceled, Is.False);
			Assert.That(mResultHandlerValue.IsFaulted, Is.False);
			Assert.That(mResultHandlerValue.Result, Is.EqualTo(result));
			Assert.That(handlerCalled, Is.EqualTo(1));
			Assert.That(handlerValue, Is.Not.Null);
			Assert.That(handlerValue.IsCanceled, Is.False);
			Assert.That(handlerValue.IsFaulted, Is.False);
			Assert.That(handlerValue.Result, Is.EqualTo(result));
		}

		[Test]
		public void Test_Serialization_BinaryFormatter()
		{
			var operation1 = new MyOperation(123);
			operation1.Acquire();
			var operation2 = TestHelper.TestBinaryFormatterRoundtrip(operation1);

			Assert.That(operation2, Is.Not.SameAs(operation1));

			_SetupProgressHandler(operation2);
			_SetupTaskHandler(operation2);

			operation1.ReportProgress(10);
			operation1.Complete("done");

			Thread.Sleep(100);

			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));

			operation2.Acquire();
		}

		[Test]
		public void Test_Serialization_DataContract()
		{
			var operation1 = new MyOperation(123);
			operation1.Acquire();
			var operation2 = TestHelper.TestDataContractRoundtrip(operation1);

			Assert.That(operation2, Is.Not.SameAs(operation1));

			_SetupProgressHandler(operation2);
			_SetupTaskHandler(operation2);

			operation1.ReportProgress(10);
			operation1.Complete("done");

			Thread.Sleep(100);

			Assert.That(mProgressHandlerCalled, Is.EqualTo(0));
			Assert.That(mResultHandlerCalled, Is.EqualTo(0));

			operation2.Acquire();
		}

	}
}

#endif
